
/**
 * Represents a single set of context characteristics.
 * 
 * @author Yong Shin
 *
 */

public class ContextCharacteristics implements Comparable<ContextCharacteristics>{
        static boolean DEBUG = false;

	/** Stores context characteristics */
	private BitSet bits;
	
	/** Stores index to BitSet for each context id */
	private static int[] cxtbsMap;
	
	/** Stores number of bits needed for each context id */
	private static int[] cxtsize;
	
	/**
	 * Calculates log base 2 value of a number.
	 * @param a
	 * @return
	 */
	private double log2(double a) {
		return Math.log(a) / Math.log(2);
	}

	public ContextCharacteristics() {

            if (cxtbsMap == null) {
                // when first ContextCharacteristic is created, initialize mappings.
                if (DEBUG) System.out.println("Initializing ContextCharacteristics context maps..");
		// set up a map between context id and the bit set.
		ContextDefinition cxtdef = ContextDefinition.getContextDefinition();
		int numcxt = cxtdef.getNumContexts();
		cxtbsMap = new int[numcxt+1];
		cxtsize = new int[numcxt];
		int bitcount = 0;
		for (int i=0; i<numcxt; i++) {
			Context cxt = cxtdef.getContext(i);
			cxtbsMap[i] = bitcount;
			cxtsize[i] = (int) Math.ceil(log2(cxt.getNumberBuckets()));
			bitcount += cxtsize[i];
		}
		
		// keep the total number of bits used in the last element of cxtbsMap array.
		cxtbsMap[numcxt] = bitcount;
                if (DEBUG) System.out.println("...done initializing context map");

            }

            bits = new BitSet();

		
	}

// copy constructor.         
        public ContextCharacteristics(ContextCharacteristics c) {
            bits = new BitSet(c.bits);
        }
	
	/**
	 * Returns bucket id used for specified context index.
	 * @param contextID
	 * @return
	 */
	public int getBucketID(int contextID) {
		
		int result = 0;
		int idx = cxtbsMap[contextID];
		int numbits = cxtsize[contextID];
		int offset = numbits-1;
		int result2;

		result2 = bits.getBits(idx, idx+numbits);

		return result2;
		/*		// bits at lower index are less significant
		while (numbits > 0) {
			result <<= 1;
			if (bits.get(idx+offset)) {
				result += 1;
			}
			numbits--;
			offset--;
		}

		if (result2 != result) {
		    System.out.println("getBuckeID: error, result is " 
				       + result + 
				       "; result2 is " + result2 +
				       "; idx is " + idx + 
				       "; offset is " + offset + 
				       "; bits is " + bits);
		}
		
		return result;
		*/

	}
	
	/**
	 * Sets bucket id for specified context.
	 * @param contextID
	 * @return
	 */
	public void setBucketID(int contextID, int bucketID) {

		int idx = cxtbsMap[contextID];
		int numbits = cxtsize[contextID];
		int offset = 0;
		
		// bits at lower index are less significant

		while (numbits > 0) {
			
			if (bucketID % 2 == 0) {
				bits.clear(idx+offset);
			} else {
				bits.set(idx+offset);
			}
			numbits--;
			offset++;
			bucketID >>= 1;
		}
	}
	
	@Override
	public int hashCode() {
		return bits.hashCode();
	}


	/**
	 * Comparison is in lexicographical order by contextID
	 */
	public int compareTo(ContextCharacteristics other) {
	    int numctx = ContextDefinition.getContextDefinition().getNumContexts();
	    for (int i=0;  i<numctx; i++) {
		int compareval = getBucketID(i) - other.getBucketID(i);
		if (compareval !=0) {
		    return compareval;
		}
	    }
	    return 0;
	}

	
	/**
	 * Two Contexts are equal only if their context id's are the same.  The context parser 
	 * must assure that no two contexts share the same context id.
	 */
	@Override
	public boolean equals(Object obj) {
		boolean result = false;
		if (obj != null) {
			if (obj.getClass() == getClass()) {
				// check if all bucket indices are identical
				ContextCharacteristics other = (ContextCharacteristics) obj;
				return  bits.equals(other.bits);
				/*
				boolean result2 = bits.equals(other.bits);
				int numctx = ContextDefinition.getContextDefinition().getNumContexts();
				boolean match = true;
				for (int i=0; match && i<numctx; i++) {
					if (getBucketID(i) != other.getBucketID(i))
						match = false;
				}
				
				if (match) {
					result = true;
				}
				if (result2 != result) {
				    System.out.println("ConextCharacteristsc.equals: error, result2 is " + result2 +
						       "; bits = " + bits + 
						       "; other.bits = " + other.bits);
						       }*/
			}
		}
      		return result;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		int numctx = ContextDefinition.getContextDefinition().getNumContexts();
                sb.append(getBucketID(0));
		for (int i=1; i<numctx; i++) {
			sb.append(" " + getBucketID(i));
		}
		return sb.toString();
	}
}
